/* Developed by Ertan Tike (ertan.tike@moreum.com) */
/* Developed by Aknazariv Zagir (aknazarov@gmail.com) */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;

namespace SimpleDayView
{
    using AppointmentList = List<Appointment>;
    using DayAppointments = Dictionary<int, List<Appointment>>;
    using System.Text;

    public class DayViewControl : Control
    {
        private const int HourLabelIndent = 2;

        private bool _isSelectionMode;
        private VScrollBar _scrollbar;
        private ContextMenu _createAppointmentMenu;
        private ContextMenu _appointmentMenu;
        private Point _currentMousePosition;

        private GridCell _startSelection;
        private List<GridCell> _selectionList = new List<GridCell>();
        private IDictionary<DateTime, DayAppointments> _appDic = new Dictionary<DateTime, DayAppointments>();

        private void ResetSelection()
        {
            _startSelection = null;
            _isSelectionMode = false;
            _selectedAppointment = null;
            _selectionList.Clear();
        }

        public DayViewControl()
        {
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.ResizeRedraw, true);
            SetStyle(ControlStyles.Selectable, true);

            _scrollbar = new VScrollBar();
            _scrollbar.SmallChange = _cellHeight;
            _scrollbar.LargeChange = GetHourHeight();
            _scrollbar.Dock = DockStyle.Right;
            _scrollbar.Visible = _allowScroll;
            _scrollbar.Scroll += scrollbar_Scroll;
            AdjustScrollbar();

            Controls.Add(_scrollbar);

            _createAppointmentMenu = new ContextMenu();

            var item = new MenuItem("Create appointment", CreateAppointment);
            _createAppointmentMenu.MenuItems.Add(item);

            _appointmentMenu = new ContextMenu();
            item = new MenuItem("Edit", EditAppointment);
            _appointmentMenu.MenuItems.Add(item);
            item = new MenuItem("Delete", DeleteAppointmentHandler);
            _appointmentMenu.MenuItems.Add(item);

            Invalidate();
        }

        public void SetData(IEnumerable<Appointment> appointments)
        {
            foreach (var app in appointments)
            {
                DayAppointments dayAppointments;

                var day = new DateTime(app.StartDate.Year, app.StartDate.Month,
                                       app.StartDate.Day);

                if (!_appDic.TryGetValue(day, out dayAppointments))
                {
                    dayAppointments = new DayAppointments();

                    _appDic.Add(day, dayAppointments);
                }

                AppointmentList list;

                if (!dayAppointments.TryGetValue(app.CellIndex, out list))
                {
                    list = new AppointmentList();

                    dayAppointments.Add(app.CellIndex, list);
                }

                list.Add(app);
            }
        }

        #region Menu stuff

        private DateTime GetDateFromTime(DateTime date)
        {
            return new DateTime(date.Year, date.Month, date.Day);
        }

        private void CreateAppointment(object sender, EventArgs e)
        {
            if (_selectionList.Count == 0)
                return;

            _selectionList.Sort();

            Appointment newAppointment = null;

            var cellIndex = _selectionList[0].CellIndex;
            var timeStart = _selectionList[0].Time;
            var timeEnd = _selectionList[_selectionList.Count - 1].Time.AddMinutes((int)MinuteInCell);

            if (AppointmentCreatorDel != null)
                newAppointment = AppointmentCreatorDel(cellIndex, timeStart, timeEnd);
            else
            {
                newAppointment = new Appointment();
                newAppointment.CellIndex = cellIndex;
                newAppointment.StartDate = timeStart;
                newAppointment.EndDate = timeEnd;
                newAppointment.Title = "Title";
                newAppointment.Description = "Description";
            }

            if (newAppointment == null)
                return;

            RaiseNewAppointment(newAppointment);

            var day = GetDateFromTime(newAppointment.StartDate);

            DayAppointments list;

            if (!_appDic.TryGetValue(day, out list))
            {
                list = new DayAppointments();

                _appDic.Add(day, list);
            }

            AppointmentList appList;

            if (!list.TryGetValue(cellIndex, out appList))
            {
                appList = new AppointmentList();
                list.Add(cellIndex, appList);
            }

            appList.Add(newAppointment);
        }

        private void EditAppointment(object sender, EventArgs e)
        {
            var x = _currentMousePosition.X;
            var y = _currentMousePosition.Y;
            var cell = GetCell(x, y);

            if (cell == null)
                return;

            var appointment = GetAppointmentAt(x, y);

            if (appointment == null)
                return;

            if (EditAppointmentDel != null)
                EditAppointmentDel(appointment);
            else
            {
                var form = new EditAppointmentForm();
                form.SetData(appointment);
                form.ShowDialog();
            }

            Invalidate();
        }

        private void DeleteAppointmentHandler(object sender, EventArgs e)
        {
            var x = _currentMousePosition.X;
            var y = _currentMousePosition.Y;
            var cell = GetCell(x, y);

            if (cell == null)
                return;

            var appointment = GetAppointmentAt(x, y);

            if (appointment == null)
                return;

            bool delete;

            if (DeleteAppointmentDel == null)
            {
                string str = string.Format("Do you want to delete appointment '{0}'", appointment.Title);
                delete = MessageBox.Show(str, "", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes;
            }
            else
                delete = DeleteAppointmentDel(appointment);

            if (!delete)
                return;

            DeleteAppointmentFromCache(appointment);

            Invalidate();
        }

        private void DeleteAppointmentFromCache(Appointment appointment)
        {
            RaiseDeleteAppointment(appointment);

            var date = GetDateFromTime(appointment.StartDate);

            if (!_appDic.ContainsKey(date))
                return;

            foreach (var pair in _appDic[date].Where(pair => pair.Key == appointment.CellIndex))
                pair.Value.Remove(appointment);
        }

        #endregion

        #region Properties

        private int _hourLabelWidth = 50;
        [System.ComponentModel.DefaultValue(50)]
        public int HourLabelWidth
        {
            get { return _hourLabelWidth; }
            set
            {
                if (value <= 0)
                    return;

                _hourLabelWidth = value;

                Invalidate();
            }
        }

        private int _cellHeight = 18;
        [System.ComponentModel.DefaultValue(18)]
        public int CellHeight
        {
            get { return _cellHeight; }
            set
            {
                if (value <= 0)
                    return;

                _cellHeight = value;
                OnHalfHourHeightChanged();
            }
        }

        private int _dayHeadersHeight = 20;
        [System.ComponentModel.DefaultValue(20)]
        public int DayHeadersHeight
        {
            get { return _dayHeadersHeight; }
            set
            {
                if (value < 0)
                    return;

                _dayHeadersHeight = value;
                Invalidate();
            }
        }

        private int _daysToShow = 1;
        [System.ComponentModel.DefaultValue(1)]
        public int DaysToShow
        {
            get { return _daysToShow; }
            set
            {
                if (value <= 0)
                    return;

                _daysToShow = value;
                Invalidate();
            }
        }

        private DateTime _startDate;
        public DateTime StartDate
        {
            get { return _startDate; }
            set
            {
                _startDate = value;
                OnStartDateChanged();
            }
        }

        private int _hourStart = 9;
        [System.ComponentModel.DefaultValue(9)]
        public int HourStart
        {
            get { return _hourStart; }
            set
            {
                if ((value < 0) || (value > 24))
                    return;

                _hourStart = value;
                Invalidate();
            }
        }

        private int _hourEnd = 18;
        [System.ComponentModel.DefaultValue(18)]
        public int HourEnd
        {
            get { return _hourEnd; }
            set
            {
                if ((value <= 0) || (value >= 24))
                    return;

                _hourEnd = value;
                Invalidate();
            }
        }

        private Appointment _selectedAppointment;
        [System.ComponentModel.Browsable(false)]
        public Appointment SelectedAppointment
        {
            get { return _selectedAppointment; }
        }

        private DateTime _selectionStart;
        public DateTime SelectionStart
        {
            get { return _selectionStart; }
            set { _selectionStart = value; }
        }

        private DateTime _selectionEnd;
        public DateTime SelectionEnd
        {
            get { return _selectionEnd; }
            set { _selectionEnd = value; }
        }

        private int _appointmentGripWidth = 5;
        [System.ComponentModel.DefaultValue(5)]
        public int AppointmentGripWidth
        {
            get { return _appointmentGripWidth; }
            set
            {
                if (value < 0)
                    return;

                _appointmentGripWidth = value;
                Invalidate();
            }
        }

        public bool SelectedAppointmentIsNew { get; private set; }

        private bool _allowScroll = true;
        [System.ComponentModel.DefaultValue(true)]
        public bool AllowScroll
        {
            get { return _allowScroll; }
            set { _allowScroll = value; }
        }

        private bool _allowInplaceEditing = true;
        [System.ComponentModel.DefaultValue(true)]
        public bool AllowInplaceEditing
        {
            get { return _allowInplaceEditing; }
            set { _allowInplaceEditing = value; }
        }

        private bool _allowNew = true;
        [System.ComponentModel.DefaultValue(true)]
        public bool AllowNew
        {
            get { return _allowNew; }
            set { _allowNew = value; }
        }

        private MinuteInCellEnum _minuteInCell = MinuteInCellEnum.M30;
        [System.ComponentModel.DefaultValue(MinuteInCellEnum.M30)]
        public MinuteInCellEnum MinuteInCell
        {
            get { return _minuteInCell; }
            set
            {
                _minuteInCell = value;
                Invalidate();
            }
        }

        private bool _showSubHeader;
        [System.ComponentModel.DefaultValue(false)]
        public bool ShowSubHeader
        {
            get { return _showSubHeader; }
            set
            {
                _showSubHeader = value;
                Invalidate();
            }
        }

        private int _subHeaderHeight = 20;
        [System.ComponentModel.DefaultValue(20)]
        public int SubHeaderHeight
        {
            get { return _subHeaderHeight; }
            set
            {
                if (value < 0)
                    return;

                _subHeaderHeight = value;
                Invalidate();
            }
        }

        public int GetHourHeight()
        {
            return CellHeight * GetCellCountInHour();
        }

        public int GetCellCountInHour()
        {
            return 60 / (int)MinuteInCell;
        }

        private int GetHeaderHeight()
        {
            int subHeader = 0;

            if (ShowSubHeader)
                subHeader = SubHeaderHeight;

            return DayHeadersHeight + subHeader;
        }

        private Color _backAreaColor = Color.FromArgb(213, 228, 242);
        public Color BackAreaColor
        {
            get { return _backAreaColor; }
            set { _backAreaColor = value; }
        }

        private Font _hourFont = new Font(Control.DefaultFont.FontFamily, 14, FontStyle.Regular);
        public Font HourFont
        {
            get { return _hourFont; }
            set { _hourFont = value; }
        }

        private Font _minuteFont = new Font(Control.DefaultFont.FontFamily, 8, FontStyle.Regular);
        public Font MinuteFont
        {
            get { return _minuteFont; }
            set { _minuteFont = value; }
        }

        private Color _hourSeperatorColor = Color.FromArgb(165, 191, 225);
        public Color HourSeperatorColor
        {
            get { return _hourSeperatorColor; }
            set { _hourSeperatorColor = value; }
        }

        private Color _selectionColor = Color.FromArgb(41, 76, 122);
        public Color SelectionColor
        {
            get { return _selectionColor; }
            set { _selectionColor = value; }
        }

        private Color _workingHourColor = Color.FromArgb(255, 255, 255);
        public Color WorkingHourColor
        {
            get { return _workingHourColor; }
            set { _workingHourColor = value; }
        }

        private Color _halfHourSeperatorColor = Color.FromArgb(165, 191, 225);
        public Color HalfHourSeperatorColor
        {
            get { return _halfHourSeperatorColor; }
            set { _halfHourSeperatorColor = value; }
        }

        private HourFormatEnum _hourFormat = HourFormatEnum.H_24;
        [System.ComponentModel.DefaultValue(HourFormatEnum.H_24)]
        public HourFormatEnum HourFormat
        {
            get { return _hourFormat; }
            set
            {
                _hourFormat = value;
                Invalidate();
            }
        }

        #endregion

        private void OnHalfHourHeightChanged()
        {
            AdjustScrollbar();
            Invalidate();
        }

        protected virtual void OnStartDateChanged()
        {
            _startDate = _startDate.Date;

            _selectedAppointment = null;
            SelectedAppointmentIsNew = false;

            Invalidate();
        }

        public int GetSubHeaderCount()
        {
            if (!ShowSubHeader)
                return 1;

            if (_subHeaderNames.Count == 0)
                return 1;

            return _subHeaderNames.Count;
        }

        private List<string> _subHeaderNames = new List<string>();

        private AppointmentSizeModificationEnum _appointmentSizeModification = AppointmentSizeModificationEnum.NONE;

        private enum AppointmentSizeModificationEnum
        {
            NONE,
            TOP,
            BOTTOM
        }

        #region Event Handlers

        private void scrollbar_Scroll(object sender, ScrollEventArgs e)
        {
            Invalidate();
        }

        protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
        {
            base.SetBoundsCore(x, y, width, height, specified);
            AdjustScrollbar();
        }

        private void AdjustScrollbar()
        {
            _scrollbar.Maximum = (GetHourHeight() * (HourEnd - HourStart + 1)) - Height + GetHeaderHeight();
            _scrollbar.Minimum = 0;
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            Focus();

            if (e.Button == MouseButtons.Left)
                ProcessLeftButtonDown(e);

            base.OnMouseDown(e);
        }

        private bool _appointmentResozing;

        private void ProcessLeftButtonDown(MouseEventArgs e)
        {
            _appointmentResozing = false;

            var curSelectedAppointment = GetAppointmentAt(e.X, e.Y);

            if (curSelectedAppointment != _selectedAppointment)
            {
                ResetSelection();
                _selectedAppointment = curSelectedAppointment;

                var eventArg = new AppointmentEventArgs(_selectedAppointment);
                RaiseSelectionChanged(eventArg);

                if (_selectedAppointment != null)
                {
                    _appointmentSizeModification = ProcessAppointmentHover(e, _selectedAppointment);
                    if (_appointmentSizeModification != AppointmentSizeModificationEnum.NONE)
                        _appointmentResozing = true;
                }
            }
            else
            {
                ResetSelection();

                var cell = GetCell(e.X, e.Y);

                if (cell != null)
                {
                    _isSelectionMode = true;

                    _selectionList.Add(cell);

                    _startSelection = cell;
                }
            }

            Invalidate();
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            var cell = GetCell(e.X, e.Y);

            if (cell != null)
            {
                if (_isSelectionMode)
                {
                    if (cell.IsOneColumn(_selectionList[0]))
                        FillSelectionList(e.X, e.Y);
                }
                else
                {
                    if (_appointmentResozing)
                    {
                        if (_selectedAppointment != null)
                            if (_appointmentSizeModification == AppointmentSizeModificationEnum.TOP)
                                _selectedAppointment.StartDate = cell.Time;
                            else if (_appointmentSizeModification == AppointmentSizeModificationEnum.BOTTOM)
                                _selectedAppointment.EndDate = cell.Time;
                    }

                    var appointment = GetAppointmentAt(e.X, e.Y);

                    if (appointment != null)
                        _appointmentSizeModification = ProcessAppointmentHover(e, appointment);
                }
            }

            Invalidate();

            base.OnMouseMove(e);
        }

        private AppointmentSizeModificationEnum ProcessAppointmentHover(MouseEventArgs e, Appointment appointment)
        {
            var appointmentRect = GetAppointmentRectangle(appointment);

            int startY = appointmentRect.Y;
            int endY = startY + appointmentRect.Height;

            if (startY == e.Y || (startY - 2) == e.Y || (startY - 1) == e.Y)
            {
                Cursor.Current = Cursors.SizeNS;
                return AppointmentSizeModificationEnum.TOP;
            }
            else if (endY == e.Y || (endY - 2) == e.Y || (endY - 1) == e.Y)
            {
                Cursor.Current = Cursors.SizeNS;
                return AppointmentSizeModificationEnum.BOTTOM;
            }

            return AppointmentSizeModificationEnum.NONE;
        }

        private void FillSelectionList(int x, int y)
        {
            _selectionList.Clear();

            if (_startSelection == null)
                return;

            _selectionList.Add(_startSelection);

            var endCell = GetCell(x, y);

            DateTime startRange, endRange;

            GetCellRange(out startRange, out endRange);

            if (!_startSelection.IsOneColumn(endCell))
                return;

            int direction = 0;

            if (_startSelection.Time < endCell.Time)
                direction = 1;
            else if (_startSelection.Time > endCell.Time)
                direction = -1;

            if (direction == 0)
                return;

            GridCell cell;

            var startTime = _startSelection.Time;

            do
            {
                cell = new GridCell();
                cell.CellIndex = _startSelection.CellIndex;

                startTime = startTime.AddMinutes(direction * (int)MinuteInCell);

                cell.Time = startTime;

                if (cell.Time >= startRange && cell.Time < endRange)
                    _selectionList.Add(cell);

            }
            while (cell.Compare(cell, endCell) != 0);

            Invalidate();
        }

        private void GetCellRange(out DateTime startRange, out DateTime endRange)
        {
            startRange = new DateTime(_startSelection.Time.Year, _startSelection.Time.Month, _startSelection.Time.Day, HourStart, 0, 0);
            endRange = new DateTime(_startSelection.Time.Year, _startSelection.Time.Month, _startSelection.Time.Day, HourEnd, 0, 0);

            var day = GetDateFromTime(_startSelection.Time);

            if (!_appDic.ContainsKey(day))
                return;

            var dayAppointments = _appDic[day];

            if (!dayAppointments.ContainsKey(_startSelection.CellIndex))
                return;

            var appointments = dayAppointments[_startSelection.CellIndex];

            foreach (var app in appointments)
            {
                if (app.StartDate > _startSelection.Time)
                    continue;

                if (startRange < app.EndDate)
                    startRange = app.EndDate;
            }

            foreach (var app in appointments)
            {
                if (app.StartDate < _startSelection.Time)
                    continue;

                if (endRange > app.StartDate)
                    endRange = app.StartDate;
            }
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            _appointmentSizeModification = AppointmentSizeModificationEnum.NONE;
            _appointmentResozing = false;

            if (e.Button == MouseButtons.Left)
                _isSelectionMode = false;

            if (e.Button == MouseButtons.Right)
            {
                var cell = GetCell(e.X, e.Y);

                if (cell != null)
                {
                    var appointment = GetAppointmentAt(e.X, e.Y);

                    if (appointment != null)
                        ShowAppointmentMenu(e);
                    else
                        ShowMenu(e, cell);
                }
            }

            base.OnMouseUp(e);
        }

        private void ShowAppointmentMenu(MouseEventArgs e)
        {
            _currentMousePosition = new Point(e.X, e.Y);
            _appointmentMenu.Show(this, new Point(e.X, e.Y));
        }

        private void ShowMenu(MouseEventArgs e, GridCell cell)
        {
            if (_selectionList.Count == 0)
                return;

            if (!_selectionList.Contains(cell))
                return;

            _createAppointmentMenu.Show(this, new Point(e.X, e.Y));
        }

        private void RaiseSelectionChanged(AppointmentEventArgs e)
        {
            if (SelectionChanged != null)
                SelectionChanged(this, e);
        }

        private void RaiseAppointmentMove(AppointmentEventArgs e)
        {
            if (AppoinmentMove != null)
                AppoinmentMove(this, e);
        }

        private void RaiseNewAppointment(Appointment appointment)
        {
            var args = new AppointmentEventArgs(appointment);

            if (AppointmentCreated != null)
                AppointmentCreated(this, args);

            _selectedAppointment = null;
            SelectedAppointmentIsNew = false;

            Invalidate();
        }

        private void RaiseDeleteAppointment(Appointment appointment)
        {
            var args = new AppointmentEventArgs(appointment);

            if (AppointmentDeleted != null)
                AppointmentDeleted(this, args);
        }

        #endregion

        #region Public Methods

        public int GetDayWidth()
        {
            var t = Width - (_scrollbar.Width + HourLabelWidth) - (_daysToShow - 1) * AppointmentGripWidth;
            var width = t / _daysToShow;

            return width;
        }

        public GridCell GetCell(int x, int y)
        {
            if (y < GetHeaderHeight())
                return null;

            if (x > Width - _scrollbar.Width)
                return null;

            int dayWidth = GetDayWidth();

            int headerHeight = GetHeaderHeight();
            int hourHeight = GetHourHeight();

            int val = (y - headerHeight + _scrollbar.Value);

            int hour = val / hourHeight + HourStart;

            x -= HourLabelWidth;

            DateTime date = StartDate;

            date = date.AddHours(hour);

            int dayToAdd = x / (dayWidth + AppointmentGripWidth);

            date = date.AddDays(dayToAdd);

            int subHeadeWidth = dayWidth / GetSubHeaderCount();

            int cellIndex = (x - (dayToAdd * dayWidth + dayToAdd * AppointmentGripWidth)) / subHeadeWidth;

            int selectedCellInOur = (y - headerHeight + _scrollbar.Value) % GetHourHeight();
            int min = selectedCellInOur / CellHeight;

            date = date.AddMinutes(min * (int)MinuteInCell);

            var gridCell = new GridCell();

            gridCell.CellIndex = cellIndex;
            gridCell.Time = date;

            return gridCell;
        }

        public Appointment GetAppointmentAt(int x, int y)
        {
            var cell = GetCell(x, y);

            if (cell == null)
                return null;

            var day = GetDateFromTime(cell.Time);

            if (!_appDic.ContainsKey(day))
                return null;

            foreach (var categories in _appDic[day])
            {
                if (cell.CellIndex != categories.Key)
                    continue;

                foreach (var appointment in categories.Value)
                {
                    if (cell.Time >= appointment.StartDate)
                    {
                        if (cell.Time < appointment.EndDate)
                            return appointment;
                    }
                }
            }

            return null;
        }

        public void SetSubHeaders(IEnumerable<string> subHeaderNames)
        {
            _subHeaderNames.Clear();

            _subHeaderNames.AddRange(subHeaderNames);
        }

        #endregion

        #region Drawing Methods

        protected override void OnPaint(PaintEventArgs e)
        {
            using (var backBrush = new SolidBrush(BackAreaColor))
                e.Graphics.FillRectangle(backBrush, ClientRectangle);

            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

            var rectangle = new Rectangle(0, 0, Width - _scrollbar.Width, Height);

            DrawHourLabels(e, rectangle);

            if (e.ClipRectangle.IntersectsWith(rectangle))
            {
                var newDayRect = rectangle;
                newDayRect.X += HourLabelWidth;
                DrawDays(e, newDayRect);
                DrawGripper(e, newDayRect);
            }
        }

        private void DrawHourLabels(PaintEventArgs e, Rectangle rect)
        {
            rect.Y += GetHeaderHeight();

            e.Graphics.SetClip(rect);

            for (int hour = 0; hour <= HourEnd - HourStart; hour++)
            {
                Rectangle hourRectangle = rect;

                hourRectangle.Y = rect.Y + (hour * GetHourHeight()) - _scrollbar.Value;
                hourRectangle.X += HourLabelIndent;
                hourRectangle.Width = HourLabelWidth;

                if (hourRectangle.Y > GetHeaderHeight() / 2)
                    DrawHourLabel(e.Graphics, hourRectangle, hour + HourStart);

                DrawHour(rect, e, hour);
            }

            e.Graphics.ResetClip();
        }

        private void DrawHourLabel(Graphics g, Rectangle rect, int hour)
        {
            var minuteHeight = (int)g.MeasureString("00", MinuteFont).Height;

            using (var pen = new Pen(HourSeperatorColor))
                g.DrawLine(pen, rect.Left, rect.Y, rect.Width, rect.Y);

            using (var brush = new SolidBrush(HourSeperatorColor))
            {
                var dHour = hour;

                if (HourFormat == HourFormatEnum.H_12)
                    if (dHour > 12)
                        dHour -= 12;

                var hourStr = new StringBuilder();

                if (dHour < 10)
                    hourStr.Append(" ");

                hourStr.Append(dHour);

                g.DrawString(hourStr.ToString(), HourFont, brush, rect);

                rect.X += 27;

                g.DrawString("00", MinuteFont, brush, rect);

                if (HourFormat == HourFormatEnum.H_12)
                {
                    var hs = hour <= 12 ? "am" : "pm";

                    var hsRect = rect;

                    hsRect.Y += minuteHeight - 5;

                    g.DrawString(hs, MinuteFont, brush, hsRect.X, hsRect.Y);
                }
            }
        }

        private Rectangle GetHourRectangle(GridCell time, Rectangle baseRectangle)
        {
            var rect = baseRectangle;

            int hourHeight = GetHourHeight();

            int minOffset = CellHeight * time.Time.Minute / (int)MinuteInCell;

            int startY = (time.Time.Hour - HourStart) * hourHeight + minOffset;

            rect.Y = startY - _scrollbar.Value + GetHeaderHeight();

            rect.Height = CellHeight;

            int cellWidth = GetDayWidth() / GetSubHeaderCount();

            rect.X += cellWidth * time.CellIndex;

            rect.Width = cellWidth;

            return rect;
        }

        private Rectangle GetAppointmentRectangle(Appointment appointment)
        {
            var rect = new Rectangle();

            var dayDiff = _startDate - appointment.StartDate;

            int dayWidth = GetDayWidth();

            int startX = HourLabelWidth + dayWidth * dayDiff.Days + AppointmentGripWidth * dayDiff.Days;
            rect.X = startX;
            rect.Width = dayWidth;

            int hourHeight = GetHourHeight();

            int minOffset = CellHeight * appointment.StartDate.Minute / (int)MinuteInCell;
            int startY = ((appointment.StartDate.Hour - HourStart) * hourHeight) + minOffset;
            int hourDiff = appointment.EndDate.Hour - appointment.StartDate.Hour;
            minOffset = CellHeight * ((appointment.EndDate.Minute - appointment.StartDate.Minute) / (int)MinuteInCell);
            int endY = (hourDiff * hourHeight) + minOffset;

            rect.Y = startY - _scrollbar.Value + GetHeaderHeight();

            rect.Height = endY;

            return rect;
        }

        private IEnumerable<GridCell> GetSelectedList(DateTime day)
        {
            return (from iter in _selectionList
                    where iter.Time.Day == day.Day
                    where iter.Time.Month == day.Month
                    where iter.Time.Year == day.Year
                    select iter).ToList();
        }

        private void DrawDay(PaintEventArgs e, Rectangle rect, DateTime time)
        {
            var list = GetSelectedList(time);

            foreach (var iter in list)
            {
                var selectionRectangle = GetHourRectangle(iter, rect);

                DrawHourRange(e.Graphics, selectionRectangle, false, true);
            }

            DrawAppointments(e, rect, time);
        }

        private void DrawHourRange(Graphics g, Rectangle rect, bool drawBorder, bool hilight)
        {
            using (var brush = new SolidBrush(hilight ? SelectionColor : WorkingHourColor))
                g.FillRectangle(brush, rect);

            if (drawBorder)
                g.DrawRectangle(SystemPens.WindowFrame, rect);
        }

        private void DrawHour(Rectangle rect, PaintEventArgs e, int hour)
        {
            int hourHeight = GetHourHeight();

            using (var pen = new Pen(HalfHourSeperatorColor))
            {
                int y = rect.Top + hour * hourHeight - _scrollbar.Value;

                e.Graphics.DrawLine(pen, rect.Left, y, rect.Right, y);

                y += hourHeight;

                e.Graphics.DrawLine(pen, rect.Left, y, rect.Right, y);
            }

            int yIter = rect.Top + hour * hourHeight - _scrollbar.Value;

            for (int i = 1; i < GetCellCountInHour(); i++)
            {
                int y = yIter + i * CellHeight;

                using (var pen = new Pen(HourSeperatorColor))
                    e.Graphics.DrawLine(pen, rect.Left + HourLabelWidth, y, rect.Right, y);
            }
        }

        private void DrawAppointments(PaintEventArgs e, Rectangle rect, DateTime time)
        {
            var curDate = GetDateFromTime(time);

            if (!_appDic.ContainsKey(curDate))
                return;

            var categoryWidth = GetDayWidth() / GetSubHeaderCount();

            foreach (var iter in _appDic[curDate])
            {
                int x = rect.X + iter.Key * categoryWidth;

                foreach (var appointment in iter.Value)
                {
                    var appRect = GetAppointmentRectangle(appointment);

                    appRect.X = x;
                    appRect.Width = categoryWidth;

                    e.Graphics.SetClip(appRect);

                    appointment.DrawAppointment(e.Graphics, appRect, appointment == _selectedAppointment,
                                                AppointmentGripWidth, Font);

                    e.Graphics.ResetClip();
                }
            }
        }

        private void DrawDays(PaintEventArgs e, Rectangle rect)
        {
            var dayWidth = GetDayWidth();

            var time = _startDate;
            rect.Width = dayWidth;
            int subHeaderWidth = dayWidth / GetSubHeaderCount();

            for (var day = 0; day < _daysToShow; day++)
            {
                DrawDay(e, rect, time);

                DrawHeader(time, subHeaderWidth, e, rect);

                time = time.AddDays(1);
                rect.X += dayWidth + AppointmentGripWidth;
            }
        }

        private void DrawHeader(DateTime time, int subHeaderWidth, PaintEventArgs e, Rectangle rect)
        {
            var headerRect = rect;
            headerRect.Height = DayHeadersHeight;

            var headerTitle = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.GetDayName(time.DayOfWeek);

            DrawHeaderRect(e.Graphics, headerRect, headerTitle, time.Date.Equals(DateTime.Now.Date));

            using (var pen = new Pen(HalfHourSeperatorColor))
            {
                int x = rect.X;

                e.Graphics.DrawLine(pen, x, rect.Y + DayHeadersHeight, x, rect.Bottom);

                if (ShowSubHeader)
                {
                    foreach (var name in _subHeaderNames)
                    {
                        var subHeaderRect = rect;
                        subHeaderRect.Height = SubHeaderHeight;
                        subHeaderRect.Width = subHeaderWidth;
                        subHeaderRect.X = x;
                        subHeaderRect.Y += DayHeadersHeight;

                        DrawHeaderRect(e.Graphics, subHeaderRect, name);
                        x += subHeaderWidth;

                        e.Graphics.DrawLine(pen, x, rect.Y + DayHeadersHeight, x, rect.Bottom);
                    }
                }
            }
        }

        private void DrawHeaderRect(Graphics g, Rectangle rect, string text)
        {
            DrawHeaderRect(g, rect, text, false);
        }

        private void DrawHeaderRect(Graphics g, Rectangle rect, string headerTitle, bool isSelected)
        {
            var format = new StringFormat
            {
                Alignment = StringAlignment.Center,
                FormatFlags = StringFormatFlags.NoWrap,
                LineAlignment = StringAlignment.Center
            };

            var formatdd = new StringFormat
            {
                Alignment = StringAlignment.Near,
                FormatFlags = StringFormatFlags.NoWrap,
                LineAlignment = StringAlignment.Center
            };

            using (var brush = new SolidBrush(BackColor))
                g.FillRectangle(brush, rect);

            using (var aPen = new Pen(Color.FromArgb(205, 219, 238)))
                g.DrawLine(aPen, rect.Left, rect.Top + rect.Height / 2, rect.Right, rect.Top + rect.Height / 2);

            using (var aPen = new Pen(Color.FromArgb(141, 174, 217)))
                g.DrawRectangle(aPen, rect);

            var topPart = new Rectangle(rect.Left + 1, rect.Top + 1, rect.Width - 2, (rect.Height / 2) - 1);
            var lowPart = new Rectangle(rect.Left + 1, rect.Top + (rect.Height / 2) + 1, rect.Width - 1, (rect.Height / 2) - 1);

            using (var agb = new LinearGradientBrush(topPart, Color.FromArgb(228, 236, 246), Color.FromArgb(214, 226, 241), LinearGradientMode.Vertical))
                g.FillRectangle(agb, topPart);

            using (var agb = new LinearGradientBrush(lowPart, Color.FromArgb(194, 212, 235), Color.FromArgb(208, 222, 239), LinearGradientMode.Vertical))
                g.FillRectangle(agb, lowPart);

            if (isSelected)
            {
                topPart.Inflate(-topPart.Width / 4 + 1, 1); //top left orange area
                topPart.Offset(rect.Left - topPart.Left + 1, 1);
                topPart.Inflate(1, 0);
                using (var agb = new LinearGradientBrush(topPart, Color.FromArgb(247, 207, 114), Color.FromArgb(251, 230, 148), LinearGradientMode.Horizontal))
                {
                    topPart.Inflate(-1, 0);
                    g.FillRectangle(agb, topPart);
                }

                topPart.Offset(rect.Right - topPart.Right, 0);        //top right orange
                topPart.Inflate(1, 0);
                using (var agb = new LinearGradientBrush(topPart, Color.FromArgb(251, 230, 148), Color.FromArgb(247, 207, 114), LinearGradientMode.Horizontal))
                {
                    topPart.Inflate(-1, 0);
                    g.FillRectangle(agb, topPart);
                }

                using (var aPen = new Pen(Color.FromArgb(128, 240, 154, 30))) //center line
                    g.DrawLine(aPen, rect.Left, topPart.Bottom - 1, rect.Right, topPart.Bottom - 1);

                topPart.Inflate(0, -1);
                topPart.Offset(0, topPart.Height + 1); //lower right
                using (var agb = new LinearGradientBrush(topPart, Color.FromArgb(240, 157, 33), Color.FromArgb(250, 226, 142), LinearGradientMode.BackwardDiagonal))
                    g.FillRectangle(agb, topPart);

                topPart.Offset(rect.Left - topPart.Left + 1, 0); //lower left
                using (var agb = new LinearGradientBrush(topPart, Color.FromArgb(240, 157, 33), Color.FromArgb(250, 226, 142), LinearGradientMode.ForwardDiagonal))
                    g.FillRectangle(agb, topPart);
                using (var aPen = new Pen(Color.FromArgb(238, 147, 17)))
                    g.DrawRectangle(aPen, rect);
            }

            g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;

            rect.Offset(2, 1);

            using (var fntDay = new Font("Segoe UI", 8))
                g.DrawString(headerTitle, fntDay, SystemBrushes.WindowText, rect, format);
        }

        private void DrawGripper(PaintEventArgs e, Rectangle rect)
        {
            for (var i = 0; i < DaysToShow; i++)
            {
                rect.X += GetDayWidth();

                using (var brush = new SolidBrush(Color.White))
                    e.Graphics.FillRectangle(brush, rect.Left, rect.Top - 1, AppointmentGripWidth, rect.Height);

                using (var pen = new Pen(Color.Black))
                    e.Graphics.DrawRectangle(pen, rect.Left, rect.Top - 1, AppointmentGripWidth, rect.Height);

                rect.X += AppointmentGripWidth;
            }
        }

        #endregion

        public event EventHandler<AppointmentEventArgs> SelectionChanged;
        public event EventHandler<AppointmentEventArgs> AppointmentCreated;
        public event EventHandler<AppointmentEventArgs> AppointmentDeleted;
        public event EventHandler<AppointmentEventArgs> AppoinmentMove;

        public delegate Appointment CreateAppointmentPredicate(int categoryIndex, DateTime start, DateTime end);
        public delegate bool DeleteAppointmentPredicate(Appointment appointment);
        public delegate void EditAppointmentPredicate(Appointment appointment);

        public CreateAppointmentPredicate AppointmentCreatorDel;
        public DeleteAppointmentPredicate DeleteAppointmentDel;
        public EditAppointmentPredicate EditAppointmentDel;

        public enum HourFormatEnum
        {
            H_12,
            H_24
        }
    }
}
